#lang racket


(require "../list.rkt"
         "../point.rkt")


(provide 
 (rename-out (make-bounding-box bbox))
 (prefix-out
  bbox.
  (combine-out min-x max-x half-x
               min-y max-y half-y
               min-z max-z half-z
               center
               left
               right
               front
               back
               top
               bottom
               corner
               
               center<-left
               center<-right
               center<-front
               center<-back
               center<-top
               center<-bottom)))

(define (bounding-box-print bb port mode)
  (write-string "bbox(" port)
  (write-string
   (string-join
    (map
     (λ (e) (format "~a" e))
     (bounding-box-points bb))
    ", ") port)
  (write-string ")" port))

(define-struct bounding-box
  (points)
  #:property prop:custom-write bounding-box-print)


(define (filter-coordinate bb coord proc)
  (apply proc (map coord (bounding-box-points bb))))

(define (filter-abs-coordinate points coord)
  (let* ((coords (map coord points))
         (abss (map abs coords)))
    (caar
     (sort
      (zip coords abss)
      (λ (e1 e2)
        (> (second e1) (second e2)))))))

(define (half-coordinate bb coord)
  (let ((min (filter-coordinate bb coord min))
        (max (filter-coordinate bb coord max)))
    (/ (+ min max) 2)))


(define (min-x bb)
  (filter-coordinate bb x min))

(define (max-x bb)
  (filter-coordinate bb x max))

(define (half-x bb)
  (half-coordinate bb x))

(define (min-y bb)
  (filter-coordinate bb y min))

(define (max-y bb)
  (filter-coordinate bb y max))

(define (half-y bb)
  (half-coordinate bb y))

(define (min-z bb)
  (filter-coordinate bb z min))

(define (max-z bb)
  (filter-coordinate bb z max))

(define (half-z bb)
  (half-coordinate bb z))


(define (center bb)
  (xyz
   (half-x bb)
   (half-y bb)
   (half-z bb)))

(define (left bb)
  (xyz
   (min-x bb)
   (half-y bb)
   (half-z bb)))

(define (right bb)
  (xyz
   (max-x bb)
   (half-y bb)
   (half-z bb)))

(define (front bb)
  (xyz
   (half-x bb)
   (min-y bb)
   (half-z bb)))

(define (back bb)
  (xyz
   (half-x bb)
   (max-y bb)
   (half-z bb)))

(define (bottom bb)
  (xyz
   (half-x bb)
   (half-y bb)
   (min-z bb)))

(define (top bb)
  (xyz
   (half-x bb)
   (half-y bb)
   (max-z bb)))

(define corner
  (case-lambda
    ((bb pa pb)
     (xyz
      (filter-abs-coordinate (list pa pb) x)
      (filter-abs-coordinate (list pa pb) y)
      (filter-abs-coordinate (list pa pb) z)))
    ((bb pa pb pc)
     (xyz
      (filter-abs-coordinate (list pa pb pc) x)
      (filter-abs-coordinate (list pa pb pc) y)
      (filter-abs-coordinate (list pa pb pc) z)))))

; edit: refactor the following functions
(define (center<-left bb p)
  (p+p p (p-p (center bb) (left bb))))

(define (center<-right bb p)
  (p+p p (p-p (center bb) (right bb))))

(define (center<-front bb p)
  (p+p p (p-p (center bb) (front bb))))

(define (center<-back bb p)
  (p+p p (p-p (center bb) (back bb))))

(define (center<-top bb p)
  (p+p p (p-p (center bb) (top bb))))

(define (center<-bottom bb p)
  (p+p p (p-p (center bb) (bottom bb))))